package com.qkun.library.base.binding.service;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.BadParcelableException;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.ResultReceiver;

import androidx.annotation.NonNull;

import com.qkun.library.base.BaseApplication;
import com.qkun.library.utils.MyLog;
import com.qkun.library.utils.RunHandler;
import com.qkun.library.utils.Utils;

import java.lang.ref.WeakReference;

import static com.qkun.library.base.binding.service.BindingProtocol.CLIENT_MSG_CONNECT;
import static com.qkun.library.base.binding.service.BindingProtocol.CLIENT_MSG_DISCONNECT;
import static com.qkun.library.base.binding.service.BindingProtocol.CLIENT_VERSION_CURRENT;
import static com.qkun.library.base.binding.service.BindingProtocol.DATA_PACKAGE_NAME;
import static com.qkun.library.base.binding.service.BindingProtocol.DATA_RESULT_RECEIVER;
import static com.qkun.library.base.binding.service.BindingProtocol.SERVICE_MSG_ON_CONNECT;
import static com.qkun.library.base.binding.service.BindingProtocol.SERVICE_MSG_ON_CONNECT_FAILED;

public abstract class BindingHandler extends RunHandler {

    private static final int CONNECT_STATE_DISCONNECTING = 0;
    private static final int CONNECT_STATE_DISCONNECTED = 1;
    private static final int CONNECT_STATE_CONNECTING = 2;
    private static final int CONNECT_STATE_CONNECTED = 3;
    private static final int CONNECT_STATE_SUSPENDED = 4;

    private final Context mContext;
    private final ComponentName mServiceComponent;

    private int mState = CONNECT_STATE_DISCONNECTED;
    private BindingServiceConnection mServiceConnection;
    // TODO: 2/18 0018 有待考虑是否真的得要用WeakReference，ps：ServiceMessenger容易被释放
    private Messenger mClientMessenger;
    private Messenger mServiceMessenger;
    private BindingToken mToken;

    public BindingHandler(@NonNull Class<? extends BindingService> cls) {
        this(BaseApplication.getAppContext(), new ComponentName(BaseApplication.getAppContext(), cls));
    }

    public BindingHandler(@NonNull ComponentName serviceComponent) {
        this(BaseApplication.getAppContext(), serviceComponent);
    }

    public BindingHandler(@NonNull Context context, @NonNull ComponentName serviceComponent) {
        super();
        mContext = context.getApplicationContext();
        mServiceComponent = serviceComponent;
    }

    private BindingToken verifyToken(BindingToken token) {
        if (Utils.isNull(mToken) || mToken.equals(token)) {
            return token;
        }
        return null;
    }

    @Override
    public final void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        if (BindingToken.isBindingToken(msg.getData().getParcelable(BindingProtocol.DATA_TOKEN))) {
            final BindingToken token = verifyToken((BindingToken) msg.getData().getParcelable(BindingProtocol.DATA_TOKEN));
            if (Utils.nonNull(token)) {
                try {
                    switch (msg.what) {
                        case SERVICE_MSG_ON_CONNECT:
                            onServiceConnected(msg.getData(), token);
                            break;
                        case SERVICE_MSG_ON_CONNECT_FAILED:
                            onConnectionFailed();
                            break;
                        default:
                            handleServiceMessage(msg);
                    }
                } catch (BadParcelableException e) {
                    MyLog.e("Unboxing failed -> what: " + msg.what, e);
                    // 如果连接时发生错误，则断开服务
                    if (msg.what == SERVICE_MSG_ON_CONNECT) {
                        onConnectionFailed();
                    }
                }
            } else {
                MyLog.e(new IllegalStateException("Token is null!!!"));
            }
        } else {
            handleMessageInternal(msg);
        }
    }

    protected abstract void handleMessageInternal(@NonNull Message msg);

    protected abstract void handleServiceMessage(@NonNull Message msg);

    protected Bundle onPreConnect() {
        return null;
    }

    protected abstract void onConnected(Bundle serviceData);

    protected abstract void onConnectSuspended();

    protected abstract void onConnectFailed();

    public final void connect() {
        if (!isDisconnect()) {
            throw new IllegalStateException("Called while neither disconnecting nor disconnected(state=" + mState + ")");
        }
        mState = CONNECT_STATE_CONNECTING;
        post(new Runnable() {
            @Override
            public void run() {
                if (mState == CONNECT_STATE_DISCONNECTING) {
                    return;
                }
                mState = CONNECT_STATE_CONNECTING;
                if (Utils.nonNull(mServiceMessenger)) {
                    throw new RuntimeException("ServiceMessenger should be null");
                }
                if (Utils.nonNull(mClientMessenger)) {
                    throw new RuntimeException("ClientMessenger should be null");
                }

                final Intent intent = new Intent(BindingService.SERVICE_INTERFACE);
                intent.setComponent(mServiceComponent);

                mServiceConnection = new BindingServiceConnection();
                boolean bound = false;
                try {
                    bound = BindingService.bindNormalService(mContext, intent, mServiceConnection);
                } catch (Exception e) {
                    MyLog.e("Failed to binding service " + mServiceComponent, e);
                }

                if (!bound) {
                    forceDisconnection();
                    onConnectFailed();
                }
            }
        });
    }

    public final void disconnect(final Bundle data) {
        mState = CONNECT_STATE_DISCONNECTING;
        post(new Runnable() {
            @Override
            public void run() {
                try {
                    sendRequest(CLIENT_MSG_DISCONNECT, data);
                } catch (RemoteException e) {
                    MyLog.e(e);
                }
                final int state = mState;
                forceDisconnection();
                //如果状态不是CONNECT_STATE_DISCONNECTING，则保留该状态，以便可以正确处理disconnect()之后的操作
                if (state != CONNECT_STATE_DISCONNECTING) {
                    mState = state;
                }
            }
        });
    }

    public final void disconnect() {
        disconnect(null);
    }

    private void forceDisconnection() {
        if (Utils.nonNull(mServiceConnection)) {
            mContext.unbindService(mServiceConnection);
        }
        mState = CONNECT_STATE_DISCONNECTED;
        mToken = null;
        mServiceConnection = null;
        mClientMessenger = null;
        mServiceMessenger = null;
    }

    private void onServiceConnected(@NonNull Bundle serviceData, BindingToken token) {
        if (isDisconnect()) {
            return;
        }
        if (mState != CONNECT_STATE_CONNECTING) {
            return;
        }
        mState = CONNECT_STATE_CONNECTED;
        mToken = token;
        onConnected(serviceData);
    }

    private void onConnectionFailed() {
        if (isDisconnect()) {
            return;
        }
        if (mState != CONNECT_STATE_CONNECTING) {
            return;
        }
        forceDisconnection();
        onConnectFailed();
    }

    protected final void sendRequestOnResult(int what, Bundle data, ResultReceiver receiver) throws RemoteException {
        if (Utils.isNull(data)) {
            data = new Bundle();
        }
        data.putParcelable(DATA_RESULT_RECEIVER, receiver);
        sendRequest(what, data);
    }

    protected final void sendRequest(int what, Bundle data) throws RemoteException {
        if (Utils.nonNull(mServiceMessenger) && Utils.nonNull(mClientMessenger)) {
            Message msg = Message.obtain();
            msg.what = what;
            msg.arg1 = CLIENT_VERSION_CURRENT;
            msg.setData(data);
            msg.getData().putParcelable(BindingProtocol.DATA_TOKEN, mToken);
            msg.replyTo = mClientMessenger;
            mServiceMessenger.send(msg);
        } else {
            MyLog.e("Non connect!");
        }
    }

    public final boolean isConnected() {
        return mState == CONNECT_STATE_CONNECTED;
    }

    public final boolean isDisconnect() {
        return mState == CONNECT_STATE_DISCONNECTING || mState == CONNECT_STATE_DISCONNECTED;
    }

    private class BindingServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, final IBinder binder) {
            postOrRun(new Runnable() {
                @Override
                public void run() {
                    if (isInactive()) {
                        return;
                    }
                    mClientMessenger = new Messenger(BindingHandler.this);
                    mServiceMessenger = new Messenger(binder);
                    mState = CONNECT_STATE_CONNECTING;
                    try {
                        Bundle data = onPreConnect();
                        if (Utils.isNull(data)) {
                            data = new Bundle();
                        }
                        data.putString(DATA_PACKAGE_NAME, mContext.getPackageName());
                        sendRequest(CLIENT_MSG_CONNECT, data);
                    } catch (RemoteException e) {
                        MyLog.e(e);
                    }
                }
            });
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            postOrRun(new Runnable() {
                @Override
                public void run() {
                    if (isInactive()) {
                        return;
                    }
                    mClientMessenger = null;
                    mServiceMessenger = null;
                    mState = CONNECT_STATE_SUSPENDED;
                    onConnectSuspended();
                }
            });
        }

        boolean isInactive() {
            return mServiceConnection != this || isDisconnect();
        }
    }

}
